package com.ssm.reactive.config;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.WebProperties.Resources;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ResponseStatusException;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;

/**
 * @see ErrorWebFluxAutoConfiguration
 */
@Slf4j
@Order(-2)
// @Component
public class GlobalErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {
    final ErrorAttributes errorAttributes;

    public GlobalErrorWebExceptionHandler(ErrorAttributes errorAttributes, Resources resources,
                                          ServerProperties serverProperties, ApplicationContext applicationContext,
                                          ObjectProvider<ViewResolver> viewResolvers,
                                          ServerCodecConfigurer serverCodecConfigurer) {
        super(errorAttributes, resources, serverProperties.getError(), applicationContext);

        this.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()));
        this.setMessageWriters(serverCodecConfigurer.getWriters());
        this.setMessageReaders(serverCodecConfigurer.getReaders());

        this.errorAttributes = errorAttributes;
    }

    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), (serverRequest -> {
            Throwable throwable = errorAttributes.getError(serverRequest);
            if (throwable instanceof ValidationException) {
                return handleValidationException((ValidationException) throwable);
            }
            if (throwable instanceof ResponseStatusException) {
                return handleResponseStatusException((ResponseStatusException) throwable);
            }
            log.error("Ops, just caught an unknown exception, " +
                    "please have a look at the stack trace of more details", throwable);
            return ServerResponse.status(INTERNAL_SERVER_ERROR).build();
        }));
    }

    Mono<ServerResponse> handleResponseStatusException(ResponseStatusException exception) {
        Error error = new Error(null, Arrays.asList(exception.getReason()));
        return ServerResponse.status(exception.getStatus()).bodyValue(error);
    }

    Mono<ServerResponse> handleValidationException(ValidationException exception) {
        Errors errors = exception.getErrors();
        List<InvalidField> invalidFields = errors.getFieldErrors().stream()
                .map(error -> new InvalidField(error.getField(), error.getDefaultMessage()))
                .collect(Collectors.toList());
        List<String> theErrors = errors.getGlobalErrors().stream()
                .map(ObjectError::getDefaultMessage)
                .collect(Collectors.toList());
        Error error = new Error(invalidFields, theErrors);
        return ServerResponse.badRequest().bodyValue(error);
    }

    @AllArgsConstructor
    static class Error {
        final List<InvalidField> invalidFields;
        final List<String> errors;
    }

    @AllArgsConstructor
    static class InvalidField {
        final String name;
        final String message;
    }

    @Getter
    @AllArgsConstructor
    static class ValidationException extends RuntimeException {
        final Errors errors;
    }
}
